//kinda-sh.c

//includes
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include "shellFunctions.h"
#include "linkedList.h"
#include "hashMap.h"

//status messages
#define runMsg "Running: "
#define stpMsg "Stopped: "
#define cntMsg "Restarting: "
#define finMsg "Finished: "

#define MAX_ACTIVE_CHILDREN 20
#define MAX_STOPPED_CHILDREN 10

//globals
LIST *statusQueue;
//pgid to program name
MAP *progNames;
//pid to pgid
//since getpgrp doesnt take args
MAP *pgids;
LIST *stoppedPgids;
LIST *bgPgids;
int flags[8];
int numChRun;

int sh_pgid;
static void child_handler(int sig, siginfo_t *siginfo, void *unused)
{
  int pid;
  int sh_gpid=getpid();
  int status;
  int chpgid;
  CHILD *chld;
  
  //in case a sigchld was lost, we'll check for all zombie children
  while (1){
    pid=waitpid(-1,&status,WNOHANG|WUNTRACED|WCONTINUED); 
    //if other children are still running
    //or no more children left, break
    if (pid==0 || (pid==-1 && errno==ECHILD))
      break;
    //error on all other pid==-1
    CHECK_ERROR(pid,waitError);
    chld=lookup(pgids,pid);
    if(chld!=NULL)
      chpgid=chld->pgid;
   
    //sigcont 
    //dont restore term control
    if(WIFCONTINUED(status)){
      //addEl(statusQueue,cntMsg);
      //addEl(statusQueue,lookup(pidMap,pid));
      numChRun++;
    }
    //do restore term control
    else{
      //signals that make child stop
      if(WIFSTOPPED(status)){
	//queue status msg
	addEl(statusQueue, stpMsg);
	addEl(statusQueue,lookup(progNames,chpgid));
	chld=calloc(1,4);
	CHECK_ALC_ERROR(chld);
	chld->pgid=chpgid;
	//add pid to stopped process list
	addEl(stoppedPgids,chld);
	numChRun--;
      }
      //termination signals
      else {
	addEl(statusQueue,finMsg);
	addEl(statusQueue,lookup(progNames,chpgid));
	//free memory
	chld=removeEntry(pgids,pid);
	chld=removeEntry(progNames,chpgid);
	numChRun--;
      }
      if(numChRun==0){
	//return terminal control to shell
	if (!flags[REDOUT])
	CHECK_ERROR(tcsetpgrp(STDOUT_FILENO,sh_gpid),tcError);
	if (!flags[REDIN])
	CHECK_ERROR(tcsetpgrp(STDIN_FILENO,sh_gpid),tcError);
	break;
      }}
  }
  return;
}


static void sigIgnore_handler(int sig, siginfo_t *siginfo, void *unused)
{
  return;
}

void setTermCont(int pgid){
  sigset_t sigMask ;
  sigset_t oldSigMask ;

  CHECK_ERROR(sigemptyset(&sigMask),"sigempty");
  CHECK_ERROR(sigaddset(&sigMask,SIGTTOU),"sigadd");
  CHECK_ERROR(sigaddset(&sigMask,SIGTTIN),"sigadd");
  //begin critical section
  CHECK_ERROR(sigprocmask(SIG_BLOCK,&sigMask,&oldSigMask),"sigsupend error");
  //set control for in and out
  if (!flags[REDOUT])
  CHECK_ERROR(tcsetpgrp(STDOUT_FILENO,pgid),"Error setting groupch");
  if (!flags[REDIN])
  CHECK_ERROR(tcsetpgrp(STDIN_FILENO,pgid),"Error setting group");
  //end signal blocking
  CHECK_ERROR(sigprocmask(SIG_SETMASK,&oldSigMask,NULL),"sigsupend error");
  //end critical section
}

//main
int main(int argc,char* argv[],char* envp[]){ 
  
  //for reading input
  char readBuffer[1025];
  char wasteBuffer[1025]; 
  int charsRead; 
  
  //for parsing
  LIST *progList;
  int options[5];
  char** curr_argv;
  int plistSize;

  //children vriables
  //for fork
  int pid;
  int child_pgid;
  //for pgid map
  CHILD *chld;
  int stopPgid;
  int pipefd[2];
  int prevPipeIn;
  int numChild;

  //for signal handling 
  struct sigaction sa;
  sigset_t sigMask ;
  sigset_t oldSigMask ;
  
  //misc
  int i;
  int pgid;
  int size;
  /*-----------------INIT---------*/

  //init status queue (global)
  statusQueue=createList();
  //init pid map, map pids to program arguments
  //
  progNames=createMap(MAX_ACTIVE_CHILDREN);
  pgids=createMap(MAX_ACTIVE_CHILDREN+MAX_STOPPED_CHILDREN);
  
  //keep track of stopped processes
  stoppedPgids=createList();
  
  bgPgids=createList();
  //help from mprotect(2) example
  //set up SIGCHLD handler
  //tells sigaction to accept 3 arg handler rather than the std one
  sa.sa_flags = SA_SIGINFO;
  sa.sa_sigaction = child_handler;
  sigemptyset(&sa.sa_mask);
  CHECK_ERROR(sigaddset(&sa.sa_mask,SIGTTOU),"sigadd");
  CHECK_ERROR(sigaddset(&sa.sa_mask,SIGTTIN),"sigadd");
  CHECK_ERROR(sigaddset(&sa.sa_mask,SIGCHLD),"sigadd");
  CHECK_ERROR(sigaction(SIGCHLD, &sa, NULL),sigactionError);
  
  //set up signal handler for signals to ignore
  //&sa might get modified, so lets reinit to be cetain
  sa.sa_flags = SA_SIGINFO;
  sa.sa_sigaction = sigIgnore_handler;
  sigemptyset(&sa.sa_mask);
  CHECK_ERROR(sigaction(SIGTSTP, &sa, NULL),sigactionError);
  CHECK_ERROR(sigaction(SIGINT, &sa, NULL),sigactionError);

  numChild=0;
  /*--------------------------------*/

  //the real action begins
  while (1){
   
    //set up signal mask
    //block signals that will mess up read and write
    CHECK_ERROR(sigemptyset(&sigMask),"sigempty");
    CHECK_ERROR(sigaddset(&sigMask,SIGCHLD),"sigadd");
    CHECK_ERROR(sigaddset(&sigMask,SIGINT),"sigadd");
    CHECK_ERROR(sigaddset(&sigMask,SIGTSTP),"sigadd");
    //begin critical section
    CHECK_ERROR(sigprocmask(SIG_BLOCK,&sigMask,&oldSigMask),"sigblock error");
    //write status messages
    for(i=0;i<getSize(statusQueue);i++){
      //write static part
      printf("%s",(char*)removeFirst(statusQueue));
      //write program name, by printing argv array
      printStringArray((char**)removeFirst(statusQueue),' ');
      printf("\n");
    }

    if(flags[JOBS]){
      size=getSize(stoppedPgids);
      //print stopped jobs
      for(i=0;i<size;i++){
	chld=removeFirst(stoppedPgids);
	pgid=chld->pgid;
	addEl(stoppedPgids,chld);
  
	printf("[%d] ",i);
      //write program name, by printing argv array
      printStringArray((char**)lookup(progNames,pgid),' ');
     printf("(stopped)");
      printf("\n");
    }
    //print bg jobs
    size=getSize(bgPgids);
    for(i=0;i<size;i++){
      chld=removeFirst(bgPgids);
	pgid=chld->pgid;
	addEl(bgPgids,chld);

	printf("[%d] ",i);
      //write program name, by printing argv array
      printStringArray((char**)lookup(progNames,pgid),' ');
     printf("(running)");
      printf("\n");
    }

    }
    //write prompt
    CHECK_ERROR(write(1,prompt,10),writeError);
    //read input
    charsRead=read(0,readBuffer,1025);
    //restore old mask
    CHECK_ERROR(sigprocmask(SIG_SETMASK,&oldSigMask,NULL),"sigsupend error");
    //end critical section

    CHECK_ERROR(charsRead,readError);
   
    //if our input exceeds 1024 chars, lets flush it all out by reading it
    if(charsRead>1024){
      //begin critical section 
      //reusing sigMask
      CHECK_ERROR(sigprocmask(SIG_BLOCK,&sigMask,&oldSigMask),"sigblock error");
      while(read(0,wasteBuffer,1025)>1024);
      CHECK_ERROR(sigprocmask(SIG_SETMASK,&oldSigMask,NULL),"sigsupend error");
      //end critical section
    }

    //read seems to read one too many chars (eof char?) 
    readBuffer[charsRead-1]='\0'; 
    charsRead--; 

    //parse input
    progList=parseInput(readBuffer,flags,options);
    
    //dont fork if input is empty (whitespace)
    if(progList==NULL){
      continue;
    }
    // fg was entered
    if(flags[FG]){
      //cast pointer back to int
      chld=removeFirst(stoppedPgids);
      if(chld==NULL){
	perror("No stopped processes");
	continue;
      }
      stopPgid=chld->pgid;
      setTermCont(stopPgid);
      
      CHECK_ERROR(sigemptyset(&sigMask),"sigempty");
      //send cont sig
      CHECK_ERROR(killpg(stopPgid,SIGCONT),killError);
      //wait for cont
      sigsuspend(&sigMask);
      //wait for child to finish
      sigsuspend(&sigMask);
      
      continue;
    }
    //bg entered
    if(flags[BG]){
      chld=removeFirst(stoppedPgids);
      if(chld==NULL){
	perror("No stopped processes");
	continue;
      }
      stopPgid=chld->pgid;
      //send cont
      //dont wait on it
      CHECK_ERROR(killpg(stopPgid,SIGCONT),killError);
      
      //add running msg to queue
      addEl(statusQueue,runMsg);
      addEl(statusQueue,curr_argv);
      //add to bg list
      addEl(bgPgids,chld);
      continue;
    }
    if(flags[JOBS]){
      continue;
    }


    plistSize=getSize(progList);
    numChild=plistSize;
    numChRun=numChild;
    for(i=0,curr_argv=removeFirst(progList);i<plistSize;i++,curr_argv=removeFirst(progList)){
      //dont create pipe for last child
      if (i!=plistSize && flags[PIPE])
	CHECK_ERROR(pipe(pipefd),pipeError);
      //create child
      pid=fork();
      
 
      //if we're child process 
      if(!pid){ 

	//change childs pgid
	//all children/pipes will now have this pgid
	//we'll set set the pgid to the first child's pid (making it group leader)
	if (i==0){
	  child_pgid=getpid();
	}
	setpgid(child_pgid,0);
	
	//foreground job gets terminal control
	//only needs to be done once (first child)
	if(flags[RUNBG]!=1 && i==0){
	  setTermCont(child_pgid);
	}
      
	//redirect out is enabled
	//duplicate std out with fd we created in parseinput
	if(flags[REDOUT])
	  CHECK_ERROR(dup2(options[OUTFD],STDOUT_FILENO),dupError);

	//redirect in is enabled
	//duplicate std in with fd we created in parseinput
	if(flags[REDIN])
	  CHECK_ERROR(dup2(options[INFD],STDIN_FILENO),dupError);
	
	//three cases
	//first, middle, last
	if(flags[PIPE]){
	  //first
	  //only write to pipe
	  if(i==0){
	    CHECK_ERROR(close(pipefd[0]),closeError);
	    CHECK_ERROR(dup2(pipefd[1],STDOUT_FILENO),dupError);
	  }
	  //last
	  //only read from prev pipe
	  else if(i==plistSize-1){
	    CHECK_ERROR(dup2(prevPipeIn,STDIN_FILENO),dupError);
	  }
	  //middle
	  //read from prev pipe, write to this pipe
	  else{
	    CHECK_ERROR(close(pipefd[0]),closeError);
	    CHECK_ERROR(dup2(prevPipeIn,STDIN_FILENO),dupError);
	    CHECK_ERROR(dup2(pipefd[1],STDOUT_FILENO),dupError);
	  

	  }
	}
	//exec
	CHECK_ERROR(execvp(curr_argv[0],curr_argv),execError); 
      }
      //adult process 

      else{
	//only set if first child
	if(i==0){
	  child_pgid=pid;
	}
	//set pg for child here too, to avoid race conditions
	setpgid(pid,child_pgid);

	if (flags[PIPE]){
	//close unused write pipe end
	CHECK_ERROR(close(pipefd[1]),closeError);
	//save pipe read end for next child
	prevPipeIn=pipefd[0];
	  }
	//insert pid->program name in map
	CHECK_ERROR(insert(progNames,child_pgid,curr_argv),"error inserting in hashmap");
	chld=calloc(1,4);
	  chld->pgid=child_pgid;
	//cast int to pointer so we can store it as void*
	CHECK_ERROR(insert(pgids,pid,chld),"error inserting in hashmap"); 	
	//waiting/recording chances need be done only on last child
	if (i==plistSize-1){
	  //foreground job
	  //only wait on child changes if running g
	  if(flags[RUNBG]!=1){
	    //wait on sigchld
	    CHECK_ERROR(sigemptyset(&sigMask),"sigempty");
	    //wait on sigchld signals until no more children left
	    //for piping when multiple children are running
	    //sigchld handler decrements numChRun everytime a child stops/exits
	    while (numChRun>0){
	      sigsuspend(&sigMask);
	    }
	  }
	  //background job
	  else{
	    //add running msg to queue
	    addEl(statusQueue,runMsg);
	    addEl(statusQueue,curr_argv);
	    addEl(bgPgids,chld);
	  }
	}
    
      }
    }
  }

}


